package com.example.synatx.stream;

import org.junit.Test;

import java.util.*;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.*;

/**
 * 用流收集数据
 */
public class CollectorTest {

    public List<Dish> dishList = new ArrayList<>();

    public void test01(){
        //菜的数量
        long dishnum = dishList.stream().collect(counting());
        //等价
        long dishnum2 = dishList.stream().count();
    }

    /**
     * minBy,maxBy
     * 查找流中的最大值最小值
     */
    public void test02(){
        Comparator<Dish> dishCalories = Comparator.comparingInt(Dish::getCalories);

        Optional<Dish> dishOptional = dishList.stream().collect(minBy(dishCalories));
    }

    /**
     * summingIntInt,summingIntLong,summingIntDouble 求和
     * averagingInt,averagingLong,averagingDouble 求平均
     */
    public void test03(){
        dishList.stream().collect(summingInt(Dish::getCalories));
        dishList.stream().collect(averagingInt(Dish::getCalories));

        //会一次计算该属性的最大值,最小值,平均值,数量
        IntSummaryStatistics calories = dishList.stream().collect(summarizingInt(Dish::getCalories));
    }

    /**
     * joining 字符串拼接
     */
    public void test04(){
        String menuNames = dishList.stream().map(t->t.getName()).collect(joining());
        //如果Dish类有一个toString()方法来返回菜肴的名称,可以用下述方法(实际上我本地不可用) ??
//        String dishNames = dishList.stream().collect(joining());

        String menuNames1 = dishList.stream().map(Dish::getName).collect(joining(","));
    }

    /**
     * 广义的归约汇总
     * 函数式编程通常提供了多种非方法来执行同一个操作
     * 建议是 尽可能为手头的问题探索不同的解决方案,但在通用方的方案里面,始终选择最专门化的一个.
     * 无论是在可读性还是性能上看,这一般都是最好的决定.
     * reducing
     */
    public void test05(){
        /*
         * reducing 第一个参数是归约操作的起始值,也是流没有元素的返回值
         * 第二个参数是 函数
         * 第三个参数 是一个 BinaryOperator,将两个项目累积成一个同类型的值,这里就是对两个值求和.
         */
        int calories = dishList.stream().collect(reducing(0,Dish::getCalories,(i,j) -> i+j));
        //收集框架的灵活性,以不同的方法执行同样的操作.
        int calories1 = dishList.stream().collect(reducing(0,Dish::getCalories,Integer::sum));

        int calories2 = dishList.stream().map(Dish::getCalories).reduce(Integer::sum).get();
        int calories3 = dishList.stream().map(Dish::getCalories).reduce(Integer::sum).orElse(0);
        int calories4 = dishList.stream().mapToInt(Dish::getCalories).sum();

    }

    /**
     * 分组
     */
    public void test06(){
        Map<String,List<Dish>> map = dishList.stream().collect(groupingBy(Dish::getType));
        Map<String,List<Dish>> map1 = dishList.stream().collect(groupingBy(dish -> {
            if(dish.getCalories()<400) return "DIET";
            else if(dish.getCalories() <= 700) return "NORMAL";
            else return "FAT";
        }));
    }

    /**
     * 多级分组,把第二个groupBy收集器传递给外层收集器来实现多级分组
     */
    public void test07(){
        Map<String,Map<String,List<Dish>>> map = dishList.stream().collect(groupingBy(Dish::getType,groupingBy(dish -> {
            if(dish.getCalories()<400) return "DIET";
            else if(dish.getCalories() <= 700) return "NORMAL";
            else return "FAT";
        })));

    }

    /**
     * 按子组收集数据,传给第一个groupBy的第二个收集器可以是任何类型,而不一定是另一个groupBy.
     */
    public void test08(){
        //菜单中每种菜有多少个
        Map<String,Long> map = dishList.stream().collect(groupingBy(Dish::getType,counting()));

        //按照菜单分组查找热量最高的菜
        //注意,如果菜单中没有某一类型的Dish,这个类型不会对应一个Optional.empty()的值,而且根本不会出现在Map的键中
        Map<String,Optional<Dish>> map1 = dishList.stream().collect(groupingBy(Dish::getType,maxBy(Comparator.comparingInt(Dish::getCalories))));

        //把收集器返回的结果转换为另一个类型,可以使用Collectors.collectingAndThen工厂方法返回的收集器
        Map<String,Dish> map2 = dishList.stream().collect(groupingBy(Dish::getType,
                //这个工厂方法接受两个参数-要转换的收集器以及转换函数,并返回另一个收集器
                collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)
        ));


    }

    /**
     * 常常和groupingBy联合使用的另一个收集器是mapping方法生成的.这个方法接受两个参数,一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来.
     * 目的是在累加之前对每个输入元素应用一个映射函数,这样就可以让接受特定类型元素的收集器适应不同类型的对象.
     */
    public void test09(){
        //返回每组菜肴的热量和
        Map<String,Integer> map1 = dishList.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));

        Map<String,Set<String>> map = dishList.stream().collect(groupingBy(Dish::getType,mapping(
                dish -> {
                    if(dish.getCalories()<400) return "DIET";
                    else if(dish.getCalories() <= 700) return "NORMAL";
                    else return "FAT";},
                toSet()
        )));

        Map<String , Set<String>> map2 = dishList.stream().collect(groupingBy(Dish::getType,mapping(
                dish -> {
                    if(dish.getCalories()<400) return "DIET";
                    else if(dish.getCalories() <= 700) return "NORMAL";
                    else return "FAT";},
                toCollection(HashSet::new)
        )));

    }

    /**
     * 分区 分区是分组的特殊情况,由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数.
     * 分区函数返回的是一个布尔值,Map类型的key值是boolean类型,只有true和false两组
     */
    public void test10(){
        Map<Boolean, List<Dish>> map = dishList.stream().collect(partitioningBy(Dish::isVegetarian));
        Map<Boolean,Map<String,List<Dish>>> map1 = dishList.stream().collect(partitioningBy(Dish::isVegetarian,groupingBy(Dish::getType)));
        Map<Boolean,Dish> map2 = dishList.stream().collect(partitioningBy(Dish::isVegetarian,collectingAndThen(
                maxBy(Comparator.comparingInt(Dish::getCalories)),
                Optional::get
        )));
    }

    @Test
    public void test11(){
        int n = 100;
        Map<Boolean,List<Integer>> map =  IntStream.rangeClosed(2,n).boxed().collect(
                partitioningBy(candiate -> isPrime(candiate))
        );
    }
    //测试某个自然数分为质数和非质数
    public boolean isPrime(int canidate){
        return IntStream.range(2,canidate).noneMatch(i->canidate%i ==0);
    }


}
